/* * Chrysalix * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * See the AUTHORS.txt file in the distribution for a full listing of * individual contributors. * * Chrysalix is free software. Unless otherwise indicated, all code in Chrysalix * is licensed to you under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Chrysalix is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.chrysalix.eclipse.focustree; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import org.eclipse.draw2d.Border; import org.eclipse.draw2d.Figure; import org.eclipse.draw2d.FigureCanvas; import org.eclipse.draw2d.FlowLayout; import org.eclipse.draw2d.FreeformLayer; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.GridData; import org.eclipse.draw2d.GridLayout; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.ImageFigure; import org.eclipse.draw2d.Label; import org.eclipse.draw2d.LayoutListener; import org.eclipse.draw2d.LayoutManager; import org.eclipse.draw2d.LineBorder; import org.eclipse.draw2d.MarginBorder; import org.eclipse.draw2d.MouseEvent; import org.eclipse.draw2d.MouseListener; import org.eclipse.draw2d.MouseMotionListener; import org.eclipse.draw2d.OrderedLayout; import org.eclipse.draw2d.Panel; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ICellEditorListener; import org.eclipse.jface.viewers.ICellEditorValidator; import org.eclipse.swt.SWT; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.chrysalix.common.ChrysalixException; import org.chrysalix.eclipse.EclipseI18n; import org.chrysalix.eclipse.Util; import org.chrysalix.eclipse.focustree.FocusTree.Column; import org.chrysalix.eclipse.focustree.FocusTree.Indicator; class FocusTreeCanvas extends FigureCanvas { final FocusTree focusTree; final FreeformLayer canvas = new FreeformLayer(); final Figure toolBar = new Figure(); final Panel focusLine = new Panel(); int focusLineOffset; boolean initialIndexIsOne; int initialCellWidth; int iconViewCellWidth; final LineBorder focusBorder, noFocusBorder; final AddButton addButton; final Dimension columnMargins; final MouseListener canvasMouseListener = new MouseListener.Stub() { final AtomicLong lastClicked = new AtomicLong( System.currentTimeMillis() ); @Override public void mouseDoubleClicked( final MouseEvent event ) { if ( !leftMouseButtonClicked( event ) ) return; mouseDoubleClickedOverCanvas( event ); lastClicked.set( System.currentTimeMillis() ); } @Override public void mouseReleased( final MouseEvent event ) { if ( !leftMouseButtonClicked( event ) ) return; getDisplay().timerExec( 400, new Runnable() { @Override public void run() { if ( System.currentTimeMillis() - lastClicked.get() > 600L ) if ( event.button == 1 && ( event.getState() & SWT.MODIFIER_MASK ) == 0 ) mouseClickedOverCanvas( event ); lastClicked.set( System.currentTimeMillis() ); } } ); } }; final MouseMotionListener canvasMouseMotionListener = new MouseMotionListener.Stub() { @Override public void mouseDragged( final MouseEvent event ) { // jpav: remove System.out.println( "canvas dragged" ); if ( !leftMouseButtonClicked( event ) ) return; // jpav: remove System.out.println( "left" ); final IFigure figure = canvas.findFigureAt( event.x, event.y ); if ( figure instanceof Cell ) cellDragged( ( Cell ) figure, event ); // Dragging focus line make cause mouse to move outside of focus line bounds, so the canvas needs to propagate mouse // events to the focus line listener else if ( focusLineMouseListener.dragging ) focusLineMouseListener.mouseDragged( event ); } @Override public void mouseMoved( final MouseEvent event ) { mouseMovedOverCanvas( event ); } }; final FocusLineMouseListener focusLineMouseListener = new FocusLineMouseListener(); final Map< Object, Object > lastFocusItemByParent = new HashMap<>(); ImageFigure mouseOverButton; CellEditor editor; EditorHandler editorHandler; Label fieldEdited; Column iconViewColumn; FocusTreeCanvas( final FocusTree focusTree, final Composite parent, final int style ) { super( parent, style ); this.focusTree = focusTree; setContents( canvas ); getViewport().setContentsTracksHeight( true ); getViewport().setContentsTracksWidth( true ); getHorizontalBar().setEnabled( false ); setHorizontalScrollBarVisibility( NEVER ); canvas.setOpaque( true ); canvas.addMouseListener( canvasMouseListener ); canvas.addMouseMotionListener( canvasMouseMotionListener ); canvas.addLayoutListener( new LayoutListener.Stub() { @Override public void postLayout( final IFigure container ) { // Make focus line extend horizontally across entire canvas final Rectangle bounds = new Rectangle( focusLine.getBounds() ); bounds.width = canvas.getBounds().width; focusLine.setBounds( bounds ); } } ); toolBar.setLayoutManager( new GridLayout() ); final ImageFigure collapseAllButton = new ImageFigure( Util.image( "collapseall.gif" ) ); toolBar.add( collapseAllButton ); collapseAllButton.setToolTip( new Label( EclipseI18n.focusTreeCollapseAllToolTip.text() ) ); collapseAllButton.addMouseListener( new MouseListener.Stub() { @Override public void mouseReleased( final MouseEvent event ) { if ( leftMouseButtonClicked( event ) ) collapseAllSelected(); } } ); final ImageFigure duplicateButton = new ImageFigure( Util.image( "duplicate.gif" ) ); toolBar.add( duplicateButton ); duplicateButton.setToolTip( new Label( EclipseI18n.focusTreeDuplicateToolTip.text() ) ); duplicateButton.addMouseListener( new MouseListener.Stub() { @Override public void mouseReleased( final MouseEvent event ) { if ( leftMouseButtonClicked( event ) ) focusTree.duplicate( focusTree.root ); } } ); toolBar.setSize( toolBar.getPreferredSize() ); toolBar.setVisible( false ); focusLine.setToolTip( new Label( EclipseI18n.focusTreeFocusLineToolTip.text() ) ); focusLine.setBackgroundColor( focusTree.viewModel.focusLineColor() ); focusLine.addMouseListener( focusLineMouseListener ); focusLine.addMouseMotionListener( focusLineMouseListener ); focusLine.setSize( 0, focusTree.viewModel.focusLineHeight() ); focusLineOffset = focusTree.viewModel.focusLineOffset(); addButton = new AddButton( Util.image( "add.gif" ) ); addButton.setToolTip( new Label( EclipseI18n.focusTreeAddToolTip.text() ) ); addButton.setSize( addButton.getPreferredSize() ); columnMargins = new Dimension( addButton.getPreferredSize() ).expand( 2, 2 ); focusBorder = new LineBorder( focusTree.viewModel.focusCellBorderColor(), focusTree.viewModel.focusLineHeight() ) { @Override public void paint( final IFigure figure, final Graphics graphics, final Insets insets ) { tempRect.setBounds( getPaintRectangle( figure, insets ) ); if ( getWidth() % 2 == 1 ) { tempRect.width--; tempRect.height--; } tempRect.shrink( getWidth() / 2, getWidth() / 2 ); graphics.setLineWidth( getWidth() ); graphics.setLineStyle( getStyle() ); graphics.setForegroundColor( getColor() ); graphics.drawRoundRectangle( tempRect, 8, 8 ); } }; noFocusBorder = new LineBorder( focusTree.viewModel.focusLineHeight() ) { @Override public void paint( final IFigure figure, final Graphics graphics, final Insets insets ) {} }; initialCellWidth = focusTree.viewModel.initialCellWidth(); iconViewCellWidth = focusTree.viewModel.iconViewCellWidth(); } Cell addCell( final Object item, final Column column ) throws ChrysalixException { // Add new cell for item final Cell cell = addCell( item, column, modelIndex( item, column ) ); // Update indexes final List< ? > children = column.cellColumn.getChildren(); for ( int ndx = cell.index + 1; ndx < children.size(); ndx++ ) { final Cell afterCell = ( Cell ) children.get( ndx ); afterCell.indexField.setText( String.valueOf( Integer.parseInt( afterCell.indexField.getText() ) + 1 ) ); afterCell.index++; } // Force layout to get cell location set column.cellColumn.getLayoutManager().layout( column.cellColumn ); getDisplay().asyncExec( new Runnable() { @Override public void run() { changeFocusCell( column, cell ); } } ); return cell; } private Cell addCell( final Object item, final Column column, final int index ) { // Create cell final Cell cell = new Cell( focusTree.viewModel.createCell( item ) ); column.cellColumn.add( cell, new GridData( SWT.FILL, SWT.DEFAULT, true, false ), index ); cell.item = item; cell.index = index; final GridLayout gridLayout = new GridLayout( 3, false ); gridLayout.marginHeight = gridLayout.marginWidth = 0; cell.setLayoutManager( gridLayout ); cell.setBorder( noFocusBorder ); cell.setBackgroundColor( focusTree.viewModel.cellBackgroundColor( item ) ); cell.setToolTip( new Label( EclipseI18n.focusTreeCellToolTip.text() ) ); // Construct cell cell.indexField = new IndexField( String.valueOf( initialIndexIsOne ? index + 1 : index ) ); cell.add( cell.indexField, new GridData( SWT.LEFT, SWT.TOP, false, false ) ); cell.indexField.setLabelAlignment( PositionConstants.LEFT ); cell.indexField.setForegroundColor( focusTree.viewModel.childIndexColor( column.item ) ); cell.indexField.setToolTip( new Label( EclipseI18n.focusTreeCellIndexToolTip.text() ) ); final Image image = iconViewShown() ? focusTree.viewModel.iconViewIcon( item ) : focusTree.viewModel.icon( item ); cell.icon = new ImageFigure(); cell.add( cell.icon, new GridData( SWT.FILL, SWT.CENTER, true, false ) ); if ( image != null ) cell.icon.setImage( image ); final Figure indicators = new Figure(); cell.add( indicators, new GridData( SWT.LEFT, SWT.TOP, false, true ) ); indicators.setLayoutManager( new FlowLayout() ); for ( final Indicator indicator : focusTree.model.indicators( item ) ) { final IndicatorButton button = new IndicatorButton( indicator.image, indicator ); indicators.add( button ); button.setToolTip( new Label( indicator.toolTip ) ); } final DeleteButton deleteButton = new DeleteButton( Util.image( "delete.png" ) ); indicators.add( deleteButton ); cell.deleteButton = deleteButton; deleteButton.setToolTip( new Label( EclipseI18n.focusTreeDeleteToolTip.text() ) ); deleteButton.setVisible( false ); // Save preferred width as minimum width before adding name, type, and value labels cell.setMinimumSize( cell.getPreferredSize() ); // Add name field try { if ( focusTree.model.hasName( item ) ) { try { cell.nameField = new NameField( focusTree.model.name( item ).toString() ); } catch ( final ChrysalixException e ) { Util.logError( e, EclipseI18n.focusTreeUnableToGetName, item ); cell.nameField = new NameField( EclipseI18n.focusTreeErrorText.text( e.getMessage() ) ); } cell.add( cell.nameField ); cell.nameField.setTextAlignment( PositionConstants.CENTER ); cell.nameField.setForegroundColor( focusTree.viewModel.cellForegroundColor( item ) ); try { cell.nameField.setToolTip( new Label( EclipseI18n.focusTreeCellNameToolTip.text( focusTree.model.qualifiedName( item ) ) ) ); } catch ( final ChrysalixException e ) { Util.logError( e, EclipseI18n.focusTreeUnableToGetQualifiedName, item ); cell.nameField.setToolTip( new Label( EclipseI18n.focusTreeErrorText.text( e.getMessage() ) ) ); } cell.setConstraint( cell.nameField, new GridData( SWT.FILL, SWT.DEFAULT, true, false, 3, 1 ) ); } } catch ( final ChrysalixException e ) { Util.logError( e, EclipseI18n.focusTreeUnableToDetermineIfItemHasName, item ); } if ( focusTree.model.hasType( item ) ) { // Add type field TypeField typeField; try { final Object type = focusTree.model.type( item ); typeField = new TypeField( type == null ? null : type.toString() ); } catch ( final ChrysalixException e ) { Util.logError( e, EclipseI18n.focusTreeUnableToGetType, item ); typeField = new TypeField( EclipseI18n.focusTreeErrorText.text( e.getMessage() ) ); } cell.add( typeField ); typeField.setTextAlignment( PositionConstants.CENTER ); typeField.setForegroundColor( focusTree.viewModel.cellForegroundColor( item ) ); typeField.setToolTip( new Label( EclipseI18n.focusTreeCellTypeToolTip.text() ) ); cell.setConstraint( typeField, new GridData( SWT.FILL, SWT.DEFAULT, true, false, 3, 1 ) ); } try { if ( focusTree.model.hasValue( item ) ) { // Add value field try { final Object value = focusTree.model.value( item ); cell.valueField = new ValueField( value == null ? null : value.toString() ); } catch ( final ChrysalixException e ) { Util.logError( e, EclipseI18n.focusTreeUnableToGetValue, item ); cell.valueField = new ValueField( EclipseI18n.focusTreeErrorText.text( e.getMessage() ) ); } cell.add( cell.valueField ); cell.valueField.setTextAlignment( PositionConstants.CENTER ); cell.valueField.setForegroundColor( focusTree.viewModel.cellForegroundColor( item ) ); cell.valueField.setToolTip( new Label( EclipseI18n.focusTreeCellValueToolTip.text() ) ); cell.setConstraint( cell.valueField, new GridData( SWT.FILL, SWT.DEFAULT, true, false, 3, 1 ) ); } } catch ( final ChrysalixException e ) {} // Make index and spacer labels the same size so icon is centered final int width = Math.max( cell.indexField.getPreferredSize().width, indicators.getPreferredSize().width ); ( ( GridData ) cell.getLayoutManager().getConstraint( cell.indexField ) ).widthHint = width; ( ( GridData ) cell.getLayoutManager().getConstraint( indicators ) ).widthHint = width; return cell; } void addColumn( final Column column ) { column.cellColumn = new CellColumn(); column.cellColumn.column = column; canvas.add( column.cellColumn ); setCellColumnLayoutManager( column ); // Get last focus cell for this column if available final Object lastFocusItem = lastFocusItemByParent.get( column.item ); // Create cells and subsequent add-after buttons try { int index = 0; for ( final Object child : focusTree.model.children( column.item ) ) { final Cell cell = addCell( child, column, index++ ); if ( column.focusCell == null && ( lastFocusItem == null || child.equals( lastFocusItem ) ) ) column.focusCell = cell; } } catch ( final ChrysalixException e ) { Util.logAndShowError( e, EclipseI18n.focusTreeUnableToGetChildren, column.item ); } updateCellPreferredWidth( column ); // Set preferred width of cells to model value for ( final Object child : column.cellColumn.getChildren() ) { final Cell cell = ( Cell ) child; final GridData gridData = ( GridData ) column.cellColumn.getLayoutManager().getConstraint( cell ); gridData.widthHint = initialCellWidth; column.cellColumn.setConstraint( cell, gridData ); } // Force layout to get cell locations set column.cellColumn.getLayoutManager().layout( column.cellColumn ); // Set bounds if ( column.focusCell != null ) column.focusCell.setSize( column.focusCell.getPreferredSize() ); if ( focusTree.columns.size() == 1 ) column.bounds.x = 0; else { final Column previousColumn = focusTree.columns.get( focusTree.columns.size() - 2 ); column.bounds.x = previousColumn.bounds.x + previousColumn.bounds.width; } final Dimension cellColumnSize = column.cellColumn.getPreferredSize(); column.cellColumn.setBounds( new Rectangle( column.bounds.x + columnMargins.width, 0, cellColumnSize.width, cellColumnSize.height ) ); column.bounds.width = cellColumnSize.width + columnMargins.width * 2; final int canvasWidth = column.bounds.x + column.bounds.width; focusLine.setSize( canvasWidth, focusLine.getSize().height ); canvas.setSize( canvasWidth, canvas.getSize().height ); // Wire cell column to show focus line tool tip on mouse-over // Note, the cell column figures are transparent, but overlap the focus line and canvas, so mouse events need to be // propagated to these other figures whenever they are also listening for the same event types propagateEvents( column ); // Focus on first cell focusCell( column, column.focusCell ); } void addItem() { final Column column = addButton.column; try { final Object item = focusTree.model.add( column.item, addButton.index ); if ( item == null ) throw new ChrysalixException( EclipseI18n.focusTreeNullReturnedFromCreate, column.item, addButton.index ); final Cell cell = addCell( item, column ); updateCellPreferredWidth( column ); if ( focusTree.model.nameEditable( cell.item ) ) { getDisplay().asyncExec( new Runnable() { @Override public void run() { try { if ( focusTree.model.hasName( cell.item ) ) editNameField( cell ); else editValueField( cell ); } catch ( final ChrysalixException e ) {} } } ); } } catch ( final ChrysalixException e ) { Util.logAndShowError( e, EclipseI18n.focusTreeUnableToCreateItem, column.item ); } } Cell cell( IFigure figure ) { while ( figure != canvas && !( figure instanceof Cell ) ) figure = figure.getParent(); return figure == canvas ? null : ( Cell ) figure; } void cellDragged( final Cell cell, final MouseEvent event ) { // jpav: remove System.out.println( "drag" ); } void changeFocusCell( final Column column, final Cell cell ) { if ( column.focusCell == cell ) { // Collapse current focus cell if expanded if ( column.focusCellExpanded ) removeColumnsAfter( column ); // Else expand focus cell if it has children else expandFocusCell( column ); } else { // Collapse current focus cell if expanded if ( column.focusCellExpanded ) removeColumnsAfter( column ); // Focus on cell focusCell( column, cell ); expandFocusCell( column ); } scrollToFocusLine(); } void collapseAllSelected() { final Column firstColumn = focusTree.columns.get( 0 ); if ( firstColumn.focusCellExpanded ) changeFocusCell( firstColumn, firstColumn.focusCell ); lastFocusItemByParent.clear(); } Column column( IFigure figure ) { while ( !( figure instanceof CellColumn ) ) figure = figure.getParent(); return ( ( CellColumn ) figure ).column; } private void deleteCell( final Cell cell ) { final CellColumn cellColumn = ( CellColumn ) cell.getParent(); cellColumn.remove( cell ); // Update indexes final List< ? > children = cellColumn.getChildren(); for ( int ndx = cell.index; ndx < children.size(); ndx++ ) { final Cell afterCell = ( Cell ) children.get( ndx ); afterCell.index--; afterCell.indexField.setText( String.valueOf( Integer.parseInt( afterCell.indexField.getText() ) - 1 ) ); } // Focus on next cell, or previous cell if last cell was deleted cellColumn.getLayoutManager().layout( cellColumn ); if ( children.size() > 1 ) { final Cell newCell = ( Cell ) children.get( Math.min( cell.index + 1, children.size() - 1 ) ); addPaintListener( new PaintListener() { @Override public void paintControl( final PaintEvent e ) { changeFocusCell( cellColumn.column, newCell ); removePaintListener( this ); } } ); changeFocusCell( cellColumn.column, newCell ); } updateCellPreferredWidth( cellColumn.column ); } private void edit( final Label fieldEdited, final CellEditor editor, final EditorHandler editorHandler ) { this.editor = editor; this.editorHandler = editorHandler; this.fieldEdited = fieldEdited; if ( editor.getControl() == null ) editor.create( this ); final Cell cell = cell( fieldEdited ); focusCell( column( cell ), cell ); getDisplay().asyncExec( new Runnable() { @Override public void run() { editor.setValue( fieldEdited.getText() ); final Rectangle fieldBounds = fieldEdited.getBounds(); final Point viewLocation = getViewport().getViewLocation(); editor.getControl().setBounds( fieldBounds.x - viewLocation.x, fieldBounds.y - viewLocation.y, fieldBounds.width, editor.getControl().computeSize( SWT.DEFAULT, SWT.DEFAULT ).y ); editor.setValidator( new ICellEditorValidator() { private final Color originalForegroundColor = editor.getControl().getForeground(); @Override public String isValid( final Object value ) { final String problem = editorHandler.problem(); if ( problem == null ) { editor.getControl().setToolTipText( null ); editor.getControl().setForeground( originalForegroundColor ); } else { editor.getControl().setToolTipText( problem ); editor.getControl().setForeground( getDisplay().getSystemColor( SWT.COLOR_RED ) ); } return problem; } } ); editor.addListener( new ICellEditorListener() { @Override public void applyEditorValue() { endEdit(); } @Override public void cancelEditor() { endEdit(); } @Override public void editorValueChanged( final boolean oldValidState, final boolean newValidState ) {} } ); editor.getControl().setVisible( true ); editor.setFocus(); editor.performSelectAll(); } } ); } void editNameField( final Cell cell ) { edit( cell.nameField, focusTree.viewModel.nameEditor( cell.item ), new EditorHandler() { @Override public Object commit() throws ChrysalixException { final Object item = focusTree.model.setName( cell.item, editor.getValue().toString() ); if ( item == null ) throw new ChrysalixException( EclipseI18n.focusTreeNullReturnedFromSetName, cell.item ); return item; } @Override public String problem() { return focusTree.model.nameProblem( cell.item, editor.getValue() == null ? null : editor.getValue().toString() ); } } ); } void editValueField( final Cell cell ) { edit( cell.valueField, focusTree.viewModel.valueEditor( cell.item ), new EditorHandler() { @Override public Object commit() throws ChrysalixException { final Object value = editor.getValue(); final Object item = focusTree.model.setValue( cell.item, value == null ? null : value.toString() ); if ( item == null ) throw new ChrysalixException( EclipseI18n.focusTreeNullReturnedFromSetValue, cell.item ); return item; } @Override public String problem() { return focusTree.model.valueProblem( cell.item, editor.getValue() == null ? null : editor.getValue().toString() ); } } ); } void endEdit() { if ( editor == null ) return; if ( editor.isValueValid() ) try { final Object item = editorHandler.commit(); final Cell cell = cell( fieldEdited ); final Column column = column( cell ); if ( cell.item != item ) { deleteCell( cell ); addCell( item, column ); } else { fieldEdited.setText( editor.getValue() == null ? null : editor.getValue().toString() ); if ( fieldEdited instanceof NameField ) { final Label toolTip = ( Label ) fieldEdited.getToolTip(); try { toolTip.setText( EclipseI18n.focusTreeCellNameToolTip.text( focusTree.model.qualifiedName( item ) ) ); } catch ( final ChrysalixException e ) { Util.logError( e, EclipseI18n.focusTreeUnableToGetQualifiedName, item ); toolTip.setText( EclipseI18n.focusTreeErrorText.text( e.getMessage() ) ); } } column.cellColumn.getLayoutManager().layout( column.cellColumn ); addPaintListener( new PaintListener() { @Override public void paintControl( final PaintEvent e ) { changeFocusCell( column, cell ); removePaintListener( this ); } } ); } } catch ( final ChrysalixException e ) { Util.logAndShowError( e, EclipseI18n.focusTreeUnableToCommitChanges, cell( fieldEdited ).item ); } editor.deactivate(); editor = null; } private void expandFocusCell( final Column column ) { try { if ( focusTree.model.childrenAddable( column.focusCell.item ) || focusTree.model.hasChildren( column.focusCell.item ) ) { focusTree.addColumn( column.focusCell.item ); column.focusCellExpanded = true; } } catch ( final ChrysalixException e ) { Util.logAndShowError( e, EclipseI18n.focusTreeUnableToDetermineIfChildrenExist, column.focusCell.item ); } } void focusCell( final Column column, final Cell focusCell ) { Util.logContextTrace(); // Collapse previous focus cell and give it a no-focus border if ( column.focusCell != null ) column.focusCell.setBorder( noFocusBorder ); // Set new focus cell and child column.focusCell = focusCell; if ( focusCell != null ) { lastFocusItemByParent.put( column.item, focusCell.item ); focusCell.setBorder( focusBorder ); } final Point cellColumnLocation = column.cellColumn.getLocation(); Util.logTrace( "cellColumnLocation: %s", cellColumnLocation ); final Rectangle focusCellBounds = ( focusCell == null ? new Rectangle() : focusCell.getBounds() ); Util.logTrace( "focusCellBounds: %s", focusCellBounds ); final Rectangle focusLineBounds = focusLine.getBounds(); Util.logTrace( "focusLineBounds: %s", focusLineBounds ); cellColumnLocation.y -= focusCellBounds.y + focusCellBounds.height / 2 - focusLineBounds.y - focusLineBounds.height / 2; column.cellColumn.setLocation( cellColumnLocation ); Util.logTrace( "cellColumnLocation: %s", cellColumnLocation ); updateBounds(); } void hideColumn( final Column column, final int columnWidth ) { updateColumnWidth( column, columnWidth, true ); } void hideIconView( final Column column ) { iconViewColumn = null; column.cellColumn.setBorder( null ); focusLine.setVisible( true ); for ( final Column col : focusTree.columns ) { col.cellColumn.setVisible( true ); } setCellColumnLayoutManager( column ); for ( final Object child : column.cellColumn.getChildren() ) { final Cell cell = ( Cell ) child; cell.setPreferredSize( null ); cell.icon.setImage( focusTree.viewModel.icon( cell.item ) ); final GridData gridData = new GridData( SWT.FILL, SWT.DEFAULT, false, false ); gridData.widthHint = column.cellWidthBeforeIconView; column.cellColumn.setConstraint( cell, gridData ); } column.cellColumn.setLocation( new Point( column.bounds.x + columnMargins.width, 0 ) ); column.cellColumn.setSize( column.cellColumn.getPreferredSize() ); focusCell( column, column.focusCell ); } private void hideMouseOverButton() { if ( mouseOverButton != null ) { mouseOverButton.setVisible( false ); mouseOverButton = null; } } boolean iconViewShown() { return iconViewColumn != null; } boolean leftMouseButtonClicked( final MouseEvent event ) { return event.button == 1 && ( event.getState() & SWT.MODIFIER_MASK ) == 0; } void modelChanged() { canvas.removeAll(); canvas.add( toolBar ); canvas.add( focusLine ); } int modelIndex( final Object item, final Column column ) throws ChrysalixException { // Find actual index of item in model int index = 0; for ( final Object child : focusTree.model.children( column.item ) ) { if ( child.equals( item ) ) return index; index++; } throw new ChrysalixException( EclipseI18n.focusTreeItemNotFound, item, column.item ); } void mouseClickedOverCanvas( final MouseEvent event ) { if ( editor != null ) endEdit(); final IFigure figure = canvas.findFigureAt( event.x, event.y ); if ( figure instanceof AddButton ) addItem(); else if ( figure instanceof DeleteButton ) { final Cell cell = cell( figure ); String name; try { name = EclipseI18n.deleteConfirmationMessage.text( focusTree.model.name( cell.item ) ); } catch ( final Exception e ) { name = EclipseI18n.focusTreeErrorText.text( e.getMessage() ); } if ( MessageDialog.openConfirm( getShell(), EclipseI18n.confirmDialogTitle.text(), name ) && focusTree.model.delete( cell.item ) ) deleteCell( cell ); } else if ( figure instanceof IndicatorButton ) { ( ( IndicatorButton ) figure ).indicator.selected( cell( figure ).item ); } else { final Cell cell = cell( figure ); if ( cell != null ) changeFocusCell( column( cell ), cell ); } } void mouseDoubleClickedOverCanvas( final MouseEvent event ) { if ( editor != null ) endEdit(); final IFigure figure = canvas.findFigureAt( event.x, event.y ); final Cell cell = cell( figure ); if ( figure instanceof IndexField ) { if ( focusTree.model.movable( cell.item ) ) { edit( ( IndexField ) figure, focusTree.viewModel.indexEditor( cell.item ), new EditorHandler() { @Override public Object commit() throws ChrysalixException { final Object value = editor.getValue(); if ( value == null ) return cell.item; final int index = Integer.parseInt( value.toString() ); final Column column = column( cell ); final Object item = focusTree.model.setIndex( cell.item, column.item, index ); if ( item == null ) throw new ChrysalixException( EclipseI18n.focusTreeNullReturnedFromSetType, cell.item ); int actualIndex = focusTree.model.indexOf( item, column.item ); if ( focusTree.viewModel.initialIndexIsOne() ) actualIndex++; if ( index != actualIndex ) editor.setValue( Integer.valueOf( actualIndex ).toString() ); return item; } @Override public String problem() { return focusTree.model.typeProblem( cell.item, editor.getValue() == null ? null : editor.getValue().toString() ); } } ); return; } } else if ( figure instanceof NameField ) { if ( focusTree.model.nameEditable( cell.item ) ) { editNameField( cell ); return; } } else if ( figure instanceof TypeField ) { if ( focusTree.model.typeEditable( cell.item ) ) { edit( ( TypeField ) figure, focusTree.viewModel.typeEditor( cell.item ), new EditorHandler() { @Override public Object commit() throws ChrysalixException { final Object type = editor.getValue(); final Object item = focusTree.model.setType( cell.item, type == null ? null : type.toString() ); if ( item == null ) throw new ChrysalixException( EclipseI18n.focusTreeNullReturnedFromSetType, cell.item ); return item; } @Override public String problem() { return focusTree.model.typeProblem( cell.item, editor.getValue() == null ? null : editor.getValue().toString() ); } } ); return; } } else if ( figure instanceof ValueField ) { if ( focusTree.model.valueEditable( cell.item ) ) { editValueField( cell ); return; } } try { if ( cell != null && ( focusTree.model.childrenAddable( cell.item ) || focusTree.model.hasChildren( cell.item ) ) ) focusTree.duplicate( cell.item ); } catch ( final ChrysalixException e ) { Util.logAndShowError( e, EclipseI18n.focusTreeUnableToDetermineIfChildrenAddableOrExist, cell.item ); } } void mouseMovedOverCanvas( final MouseEvent event ) { final IFigure figure = canvas.findFigureAt( event.x, event.y ); if ( figure instanceof CellColumn ) { addButton.column = column( figure ); if ( focusTree.model.childrenAddable( addButton.column.item ) ) { Cell lastCell = null; Cell cell = null; final Dimension addButtonSize = addButton.getSize(); final Point addButtonLocation = new Point(); Rectangle cellBounds = new Rectangle(); if ( iconViewShown() ) { for ( final Object child : figure.getChildren() ) { lastCell = ( Cell ) child; cellBounds = lastCell.getBounds(); if ( cellBounds.x > event.x && cellBounds.x - columnMargins.width <= event.x && cellBounds.y <= event.y && cellBounds.y + cellBounds.height > event.y ) { cell = lastCell; break; } } if ( cell == null ) { if ( lastCell == null ) { addButtonLocation.setLocation( 1, 1 ); addButton.index = 0; } else if ( cellBounds.x + cellBounds.width <= event.x && cellBounds.x + cellBounds.width + columnMargins.width > event.x && cellBounds.y <= event.y && cellBounds.y + cellBounds.height > event.y ) { addButtonLocation.y = cellBounds.y + ( cellBounds.height - addButtonSize.height ) / 2; addButtonLocation.x = cellBounds.x + cellBounds.width + 1; addButton.index = lastCell.index + 1; } else { hideMouseOverButton(); return; } } else { addButtonLocation.y = cellBounds.y + ( cellBounds.height - addButtonSize.height ) / 2; addButtonLocation.x = cellBounds.x - addButtonSize.width - 1; addButton.index = cell.index; } } else { for ( final Object child : figure.getChildren() ) { lastCell = ( Cell ) child; cellBounds = lastCell.getBounds(); if ( cellBounds.y > event.y && cellBounds.y - columnMargins.height <= event.y ) { cell = lastCell; break; } } final Rectangle cellColumnBounds = figure.getBounds(); addButtonLocation.x = cellColumnBounds.x + ( cellColumnBounds.width - addButtonSize.width ) / 2; if ( cell == null ) { if ( lastCell == null ) { addButtonLocation.y = 1; addButton.index = 0; } else { addButtonLocation.y = cellBounds.y + cellBounds.height + 1; addButton.index = lastCell.index + 1; } } else { addButtonLocation.y = cellBounds.y - addButtonSize.height - 1; addButton.index = cell.index; } } if ( mouseOverButton == addButton ) return; if ( mouseOverButton != null ) mouseOverButton.setVisible( false ); addButton.setLocation( addButtonLocation ); canvas.add( addButton ); addButton.setVisible( true ); mouseOverButton = addButton; return; } } else if ( figure instanceof AddButton ) return; else { final Cell cell = cell( figure ); if ( cell != null ) { if ( mouseOverButton == cell.deleteButton ) return; if ( focusTree.model.deletable( cell.item ) ) { if ( mouseOverButton != null ) mouseOverButton.setVisible( false ); cell.deleteButton.setVisible( true ); mouseOverButton = cell.deleteButton; return; } } } hideMouseOverButton(); } void moveToolBar( final int x ) { canvas.remove( toolBar ); toolBar.setLocation( new Point( x, getViewport().getClientArea().y ) ); canvas.add( toolBar ); } private void propagateEvents( final Column column ) { column.cellColumn.addMouseMotionListener( new MouseMotionListener.Stub() { @Override public void mouseDragged( final MouseEvent event ) { // jpav: remove System.out.println( "cell column dragged" ); if ( focusLineMouseListener.dragging || focusLine.containsPoint( event.getLocation() ) ) focusLineMouseListener.mouseDragged( event ); } @Override public void mouseMoved( final MouseEvent event ) { canvasMouseMotionListener.mouseMoved( event ); } } ); column.cellColumn.addMouseListener( new MouseListener.Stub() { @Override public void mouseDoubleClicked( final MouseEvent event ) { canvasMouseListener.mouseDoubleClicked( event ); } @Override public void mousePressed( final MouseEvent event ) { if ( focusLine.containsPoint( event.getLocation() ) ) focusLineMouseListener.mousePressed( event ); } @Override public void mouseReleased( final MouseEvent event ) { canvasMouseListener.mouseReleased( event ); focusLineMouseListener.mouseReleased( event ); } } ); } void removeColumn( final Column column ) { canvas.remove( column.cellColumn ); } private void removeColumnsAfter( final Column column ) { focusTree.removeColumnsAfter( column ); canvas.setSize( column.bounds.x + column.bounds.width, canvas.getSize().height ); } void scrollToFocusLine() { getDisplay().asyncExec( new Runnable() { @Override public void run() { scrollToY( focusLine.getLocation().y - focusLineOffset ); // scrollSmoothTo( getViewport().getClientArea().x, focusLine.getLocation().y - focusLineOffset ); } } ); } private void setCellColumnLayoutManager( final Column column ) { final GridLayout layout = new GridLayout(); layout.marginHeight = layout.verticalSpacing = columnMargins.height; layout.marginWidth = 0; column.cellColumn.setLayoutManager( layout ); } void setViewModel() { setBackground( focusTree.viewModel.treeBackgroundColor() ); focusBorder.setColor( focusTree.viewModel.focusCellBorderColor() ); focusLine.setBackgroundColor( focusTree.viewModel.focusLineColor() ); focusLineOffset = focusTree.viewModel.focusLineOffset(); iconViewCellWidth = focusTree.viewModel.iconViewCellWidth(); initialIndexIsOne = focusTree.viewModel.initialIndexIsOne(); initialCellWidth = focusTree.viewModel.initialCellWidth(); final int focusLineHeight = focusTree.viewModel.focusLineHeight(); focusBorder.setWidth( focusLineHeight ); noFocusBorder.setWidth( focusLineHeight ); final Rectangle focusLineBounds = new Rectangle( focusLine.getBounds() ); focusLineBounds.height = focusLineHeight; focusLine.setBounds( focusLineBounds ); } void showColumn( final Column column, final int width ) { updateColumnWidth( column, width, true ); scrollToFocusLine(); } void showIconView( final Column column ) { iconViewColumn = column; focusLine.setVisible( false ); for ( final Column col : focusTree.columns ) { if ( col != column ) col.cellColumn.setVisible( false ); } column.cellWidthBeforeIconView = 0; for ( final Object figure : column.cellColumn.getChildren() ) { final Cell cell = ( Cell ) figure; if ( column.cellWidthBeforeIconView == 0 ) column.cellWidthBeforeIconView = ( ( GridData ) column.cellColumn.getLayoutManager().getConstraint( cell ) ).widthHint; cell.icon.setImage( focusTree.viewModel.iconViewIcon( cell.item ) ); final Dimension size = cell.getPreferredSize( iconViewCellWidth, SWT.DEFAULT ); cell.setPreferredSize( size ); } final FlowLayout layout = new FlowLayout(); layout.setMinorAlignment( OrderedLayout.ALIGN_BOTTOMRIGHT ); layout.setMajorSpacing( columnMargins.width ); layout.setMinorSpacing( columnMargins.height ); column.cellColumn.setLayoutManager( layout ); column.cellColumn.setBorder( new MarginBorder( columnMargins.height, columnMargins.width, columnMargins.height, columnMargins.width ) ); updateIconViewBounds(); } void updateBounds() { Util.logContextTrace(); int minY = Short.MAX_VALUE; int maxY = 0; int canvasWidth = 0; for ( final Column column : focusTree.columns ) { final Rectangle cellColumnBounds = column.cellColumn.getBounds(); Util.logTrace( "cellColumnBounds: %s", cellColumnBounds ); canvasWidth += column.bounds.width; minY = Math.min( minY, cellColumnBounds.y ); maxY = Math.max( maxY, cellColumnBounds.y + cellColumnBounds.height ); } Util.logTrace( "canvasWidth: %d", canvasWidth ); Util.logTrace( "minY: %d", minY ); Util.logTrace( "maxY: %d", maxY ); int canvasHeight = maxY - minY; Util.logTrace( "canvasHeight: %d", canvasHeight ); final Point focusLineLocation = focusLine.getLocation(); Util.logTrace( "focusLineLocation: %s", focusLineLocation ); final int topMargin = focusLineOffset - ( focusLineLocation.y - minY ); Util.logTrace( "topMargin: %d", topMargin ); if ( topMargin > 0 ) { canvasHeight += topMargin; Util.logTrace( "canvasHeight: %d", canvasHeight ); minY -= topMargin; Util.logTrace( "minY: %d", minY ); } final int bottomMargin = focusLineLocation.y + getViewport().getClientArea().height - focusLineOffset - maxY; Util.logTrace( "bottomMargin: %d", bottomMargin ); if ( bottomMargin > 0 ) { canvasHeight += bottomMargin; Util.logTrace( "canvasHeight: %d", canvasHeight ); } final Dimension canvasBounds = canvas.getSize(); Util.logTrace( "canvasBounds: %s", canvasBounds ); if ( canvasHeight != canvasBounds.height ) { canvasBounds.height = canvasHeight; canvas.setSize( canvasBounds ); Util.logTrace( "canvasBounds: %s", canvasBounds ); for ( final Column column : focusTree.columns ) { column.bounds.height = canvasBounds.height; Util.logTrace( "column.bounds: %s", column.bounds ); } } if ( minY != 0 ) { focusLineLocation.y -= minY; focusLine.setLocation( focusLineLocation ); Util.logTrace( "focusLineLocation: %s", focusLineLocation ); for ( final Column column : focusTree.columns ) { final Point cellColumnLocation = column.cellColumn.getLocation(); cellColumnLocation.y -= minY; column.cellColumn.setLocation( cellColumnLocation ); Util.logTrace( "cellColumnLocation: %s", cellColumnLocation ); } } if ( canvasBounds.width > canvasWidth ) { canvasBounds.width = canvasWidth; canvas.setSize( canvasBounds ); Util.logTrace( "canvasBounds: %s", canvasBounds ); } focusTree.scroller.setMinWidth( canvasBounds.width ); focusTree.scroller.setOrigin( canvasBounds.width - focusTree.scroller.getClientArea().width, 0 ); } private void updateCellPreferredWidth( final Column column ) { column.cellPreferredWidth = column.cellColumn.getPreferredSize().width; } void updateColumnWidth( final Column column, final int width, final boolean visible ) { final int delta = width - column.bounds.width; column.bounds.width = width; final Dimension cellColumnSize = column.cellColumn.getSize(); column.cellColumn.setSize( cellColumnSize.width + delta, cellColumnSize.height ); for ( final Object cell : column.cellColumn.getChildren() ) ( ( GridData ) column.cellColumn.getLayoutManager().getConstraint( ( IFigure ) cell ) ).widthHint += delta; column.cellColumn.setVisible( visible ); boolean afterColumn = false; for ( final Column col : focusTree.columns ) if ( col == column ) afterColumn = true; else if ( afterColumn ) { final Point cellColumnLocation = col.cellColumn.getLocation(); cellColumnLocation.x += delta; col.cellColumn.setLocation( cellColumnLocation ); col.cellColumn.revalidate(); col.bounds.x += delta; } final Dimension canvasSize = canvas.getSize(); canvas.setSize( canvasSize.width + delta, canvasSize.height ); canvas.revalidate(); } void updateIconViewBounds() { final Dimension size = iconViewColumn.cellColumn.getPreferredSize( getViewport().getClientArea().width, SWT.DEFAULT ); iconViewColumn.cellColumn.setBounds( Rectangle.SINGLETON.setBounds( 0, 0, size.width, size.height ) ); canvas.setSize( size ); } private static class AddButton extends ImageFigure { int index; Column column; AddButton( final Image image ) { super( image ); } } class Cell extends Figure { IFigure delegate; Object item; int index; IndexField indexField; ImageFigure icon; NameField nameField; ValueField valueField; DeleteButton deleteButton; Cell( final IFigure delegate ) { this.delegate = delegate; final org.eclipse.draw2d.GridLayout layout = new org.eclipse.draw2d.GridLayout(); layout.marginHeight = layout.marginWidth = 0; super.setLayoutManager( layout ); super.add( delegate, null, 0 ); super.setConstraint( delegate, new org.eclipse.draw2d.GridData( SWT.FILL, SWT.FILL, true, true ) ); } @Override public void add( final IFigure figure, final Object constraint, final int index ) { delegate.add( figure, constraint, index ); } @Override public LayoutManager getLayoutManager() { return delegate.getLayoutManager(); } @Override public void setBackgroundColor( final Color bg ) { delegate.setBackgroundColor( bg ); } @Override public void setBorder( final Border border ) { delegate.setBorder( border ); } @Override public void setConstraint( final IFigure child, final Object constraint ) { delegate.setConstraint( child, constraint ); } @Override public void setLayoutManager( final LayoutManager manager ) { delegate.setLayoutManager( manager ); } @Override public void setToolTip( final IFigure f ) { delegate.setToolTip( f ); } } class CellColumn extends Figure { Column column; } class DeleteButton extends ImageFigure { DeleteButton( final Image image ) { super( image ); } } private interface EditorHandler { Object commit() throws ChrysalixException; String problem(); } class FocusLineMouseListener extends MouseMotionListener.Stub implements MouseListener { private int offset; boolean dragging; @Override public void mouseDoubleClicked( final MouseEvent event ) {} @Override public void mouseDragged( final MouseEvent event ) { // jpav: remove System.out.println( "focus drag" ); dragging = true; final int delta = event.y - offset - focusLine.getBounds().y; focusLineOffset += delta; focusLine.translate( 0, delta ); canvas.repaint(); for ( final Column column : focusTree.columns ) focusCell( column, column.focusCell ); scrollToFocusLine(); } @Override public void mousePressed( final MouseEvent event ) { offset = event.y - focusLine.getLocation().y; } @Override public void mouseReleased( final MouseEvent event ) { dragging = false; } } static class IndexField extends Label { IndexField( final String text ) { super( text ); } } private class IndicatorButton extends ImageFigure { final Indicator indicator; IndicatorButton( final Image image, final Indicator indicator ) { super( image ); this.indicator = indicator; } } static class NameField extends Label { NameField( final String text ) { super( text ); } } private class TypeField extends Label { TypeField( final String text ) { super( text ); } } static class ValueField extends Label { ValueField( final String text ) { super( text ); } } }